/**
d* kvvalidate
* @fileOverview 表单验证(radio,checkbox等多选控件只需在第一个上加验证规则)
* @author kevin Lv
* @email donggongai@126.com
* @version 3.0.0
* @date 2019-1-25
*/
;(function($){
	/**
	* @author kevin Lv
	* @constructor kvvalidate
	* @description 表单验证(radio,checkbox等多选控件只需在第一个上加验证规则)
	* @see The <a href="#">kevin</a>
	* @example
	* 	在页面初始化完成后调用，提交时即可自动验证，若不是submit触发需手动触发则调用$("#form").kvvalid();
	*	$("#form").kvvalidate();
	*   带回调函数的用法 ele为验证错误元素msg为错误信息每次只返回一个错误元素。
	*	$("#form").kvvalidate({callback:function(ele,msg){
						alert(ele.val());
					}});
	*   带回调函数的用法 错误信息采用alert弹出形式，默认在页面显示（存在元素id为待验证元素name+"Message"则显示在该元素若没有则追加到元素后）
	*	$("#form").kvvalidate({alert:true});
	* @since version 2.0
	*/
	$.fn.kvvalidate = function(options){
		debug("选择器["+this.selector+"]调用自定义插件 kvvalidate，调用元素数 "+this.length+" 个[jQuery "+this.jquery+"]");
		/**
		* @description {Json} 设置参数选项
		* @field
		*/
		var validatemethods = $.extend({},$.fn.kvvalidate.validatemethods);
		var options = $.extend({}, $.fn.kvvalidate.defaults, options);
		/**
		* @description 处理函数
		*/
		var formE = $(this); //表单
		var isOk = true;
		var hasFocus = false;
		var $inputs = formE.find(':input');
		var validEles = filterInvalidEle(formE, validatemethods, options); //获取需要验证的对象集合
		formE.data("validEles", validEles);
		formE.submit(function(){
			var isOk = true;
			var eleErrors = []; //错误元素集合
			$.each(validEles, function(i, obj){
				if(!validInputEle(obj, validatemethods, options, eleErrors)){
					isOk = false;
					if (!hasFocus) {
						obj.ele.focus();
						hasFocus = true;
	                }
					if (!options.errorContinue) { //设置的不是遇到错误继续，则直接返回
						return false; //true=continue，false=break。 
		            }
				}
			});
			return isOk;
		});
	};
	
	/**
	* @description 验证input元素
	* @param {obj} Object 验证的元素
	* @param {validatemethods} Object 所有需要验证的方法
	* @param {options} json 配置
	* @param {eleErrors} Object 错误元素集合
	*/
	function validInputEle(obj, validatemethods, options, eleErrors) {
		var isOk = true;
		var errEle;
		var messageEle;
		try {
			var vele = obj;
			var $this = vele.ele;
			errEle = vele.ele;
			if(!$this.hasClass("ignore") && !$this.prop("disabled")){ //未忽略 且未禁用
				if (!$this.data("ov")) {
					$this.data($this.val());
                }
				if ($this.data("ov")==$this.val()) {
	                // 无变化无需重复验证
                }
				var name = $this.attr("name");
				messageEle = $("#"+name+"Message");
				if (messageEle.length>0) {
					if (!messageEle.data("hastips")) { //内容提示区提示内容
						var tips = messageEle.text();
						messageEle.data("tips",tips);
						messageEle.data("hastips",true);
					}
				}
				$this.data("validate",true);
				var vmethods = vele.vmethods;
				$.each(vmethods, function(i, method) {
					var methodname= method.name;
					if (method.vtype=='class') { //class形式
						if (!validatemethods[methodname]($this.val())) {
							errEle = $this;
							throw $.fn.kvvalidate.validateerr($this,$.fn.kvvalidate.errormessages[methodname]);
						} else { //通过验证
							if(i == vmethods.length-1){ //所有验证都通过
								outSuccess($this, options, messageEle);
							}
						}
					} else if (method.vtype=='attr') {//属性形式
						if(hasAttr($this,methodname)){
							var params = $this.attr(methodname);
							if(params.indexOf(",") != -1){
								params = params.split(",");
							}
							if(!validatemethods[methodname]($this.val(),$this[0],params)){
								errEle = $this;
								throw $.fn.kvvalidate.validateerr($this,$.isFunction($.fn.kvvalidate.errormessages[methodname])?$.fn.kvvalidate.errormessages[methodname](params):$.fn.kvvalidate.errormessages[methodname]);
							} else {//通过验证
								if(i == vmethods.length-1){ // 所有验证都通过
									outSuccess($this, options, messageEle);
								}
							}
						}
					}
				});
			}
		} catch (e) { //捕获上面输出的异常，e为错误信息
			if (eleErrors) {
				eleErrors.push(errEle);
            }
			outError(errEle, options, messageEle, e);
			return false;
	    }finally{}
	    return isOk;
	};
	/**
	* @description 输出成功信息
	* @param {succEle} Object 验证成功元素
	* @param {options} Json 参数
	*/
	function outSuccess(succEle, options, messageEle){
		if (options && $.isFunction(options.callback)){//自定义
			options.callback(true, succEle, "验证通过", options, messageEle);
		} else if(!(options && options.alert)){ //追加
			var name = succEle.attr("name"); //id为name+Message的表示固定显示在页面上的
			if(succEle.hasClass(options.errorClass)){
				succEle.removeClass(options.errorClass); 
			}
			if (name && messageEle && messageEle.length>0) {
				if(succEle.val()){ // 有值 
					messageEle.text("");
					messageEle.removeClass(options.errorTipsClass);
					if(!succEle.hasClass('onlyerr')){ // onlyerr只显示错误信息
						messageEle.addClass(options.successTipsClass); 
					}
				} else {
					var tips = messageEle.data("tips");
					if(tips){
						messageEle.text(tips);
					}
					messageEle.removeClass(options.errorTipsClass).removeClass(options.successTipsClass);
				}  
			} else {
				succEle.next("."+options.errorTipsClass).remove();
			}
		}
	}
	/**
	* @description 输出错误信息
	* @param {errEle} Object 验证失败元素
	* @param {options} Json 参数
	*/
	function outError(errEle, options, messageEle, message){
		if (options && $.isFunction(options.callback)){//自定义
			options.callback(false, errEle, message, options, messageEle);
		} else if (options && options.alert) {//弹出错误信息
			alert(message);
		} else { //追加错误样式
			errEle.addClass(options.errorClass);
			if (messageEle.length>0){
				messageEle.text(message);
				messageEle.removeClass(options.successTipsClass).addClass(options.errorTipsClass); 
			} else {
				if (errEle.next("."+options.errorTipsClass).length>0) {
					errEle.next("."+options.errorTipsClass).text(message);
				} else {
					errEle.after("<span class='kv_err_container " + options.errorTipsClass + "'>"+message+"</span>");
				}
			}
		}
	}
	/**
	* @description 获取待验证input元素
	* @param {validateE} Object 验证父类元素
	* @param {validatemethods} Json 验证方法
	* @param {options} Json 参数
	* @returns {JSONObject} {ele:ele, vmethods:[]}验证对象，包含验证元素及验证方法 
	*/
	function filterInvalidEle(validateE, validatemethods, options){
		var validEles = new Array();
		validateE.each(function() {
	        var parE = $(this);
	        var $inputs = parE.find(':input'); //默认parE当做form来处理
	        if (parE.is(":input")) { //如果已经是input则直接验证自己
	        	var $inputs = parE;
            }
	        var parOnlySubmit = parE.data("onlysubmit"); //只提交时验证，其他事件不触发
	        $.each($inputs, function(i, obj){
	        	var $this = $(obj);
	        	if(!$this.hasClass("ignore") && !$this.prop("disabled")){
	        		var needValid = false; //是否需要验证
	        		var validEle ={};
	        		validEle.ele = $this; //验证元素
	        		validEle.vmethods = []; //验证方法,使用new Array在ie8下默认存在indexOf方法$.isEmptyObject判断为空有问题
	        		for (var methodname in validatemethods) {
	        			if ($this.hasClass(methodname)){//class形式
	        				needValid = true;
	        				var validEleMethod ={};
	        				validEleMethod.vtype='class';
	        				validEleMethod.name=methodname;
	        				validEle.vmethods.push(validEleMethod);
	        			} else {//属性形式
	        				if(hasAttr($this,methodname)){
	        					needValid = true;
	        					var validEleMethod ={};
	        					validEleMethod.vtype='attr';
	        					validEleMethod.name=methodname;
	        					validEle.vmethods.push(validEleMethod);
	        				}
	        			}
	        		}
	        		if (needValid) {
	        			validEles.push(validEle);
	        			var triggerMethod = $.fn.kvvalidate.triggerMethod; //触发验证的方法
	        			$.each(triggerMethod, function(i, method){ //绑定验证的触发事件
	        				if(!options.alert && !$this.data("onlysubmit") && !parOnlySubmit){ //非alert提示时、当前元素submit时触发，父元素未定义只submit时触发不处理否则提示过多
//	        			if(!(options.alert && method.indexOf('key') != -1) && !$this.data("onlysubmit") && !parOnlySubmit){ //alert提示时 +key事件(keyup,keydown等)不处理否则提示过多
	        					$this.off(method+".kvvalidate").on(method+".kvvalidate", function(e){
	        						var exec = true;
	        						if(method.indexOf('key')!=-1 && !$this.data("validate")){
	        							exec = false;
	        						}
	        						if(method.indexOf('key')!=-1 && e.which==9){ //Tab key事件
	        							exec = false;
	        						}
	        						if(exec){
	        							validInputEle(validEle, validatemethods, options);
	        						}
	        					});
	        				}
	        			});
	        			$this.off("click.kvvalidate").on("click.kvvalidate", function(){ //点击时移除提示
	        				var name = $this.attr("name");
	        				messageEle = $("#"+name+"Message");
	        				if (messageEle.length>0) {
	        					if(messageEle.data("hastips")){
	        						messageEle.text(messageEle.data("tips"));
	        						messageEle.removeClass(options.errorTipsClass).removeClass(options.successTipsClass);
	        					}
	        				}
	        			});
                    }
	        	}
	        });
        });
		return validEles;
	}
	/**
	* @description {Json} 直接调用验证方法
	* @field
	*/
	$.fn.kvvalid = function(options){
		debug("选择器["+this.selector+"]调用自定义插件 kvvalidate方法kvvalid，调用元素数 "+this.length+" 个[jQuery "+this.jquery+"]");
		/**
		* @description {Json} 设置参数选项
		* @field
		*/
		var validatemethods=$.extend({}, $.fn.kvvalidate.validatemethods);
		var options = $.extend({}, $.fn.kvvalidate.defaults, options);
		/**
		* @description 处理函数
		*/
		var $this = $(this);
		var isOk = true;
		var hasFocus = false;
		var eleErrors = []; //错误元素集合
		var validEles = $this.data("validEles");
		var needFiter = !validEles || $this.data("validateCache") == "off"; //缓存不存在或者不采用缓存
		
		// 判断input数目是否发生变化
		var inputLength = $this.find(':input').length; //默认parE当做form来处理
		if ($this.is(":input")) { //如果已经是input则直接验证自己
			inputLength = $this.length; 
		}
		var inputLengthData = $this.data("inputLength");
		if (!inputLengthData || inputLength != inputLengthData) { //元素发生了变化,需要重新过滤要验证的元素
			needFiter = true;
        }

		if(needFiter){
			validEles = filterInvalidEle($this, validatemethods, options, eleErrors);
			$this.addClass("_shieldjs_form_validate");
			$this.data("validEles",validEles);
			$this.data("inputLength",inputLength);
		}
		if (validEles) {
			$.each(validEles, function(i, obj){
				if (!validInputEle(obj, validatemethods, options, eleErrors)) {
					isOk = false;
					if (!hasFocus) {
						obj.ele.focus();
						hasFocus = true;
					}
					if (!options.errorContinue) { //设置的不是遇到错误继续，则直接返回
						return false; //true=continue，false=break。 
					}
				}
			});
        }
		return isOk;
		
	};
	/**
	* @description {Json} 默认参数
	* @field
	*/
	var checkFormmsgdef = "格式不正确";
	//私有方法
	/**
	* @description 输出选中对象的个数到控制台
	* @param {msg} String 输出内容
	*/
	function debug(msg) {
		if (window.console && window.console.log){
			window.console.log('kvvalidate.js log: ' + msg);
		}
	}
	function optional(value){
		return value=='';
	}	
	function checkable( element ) {
		return (/radio|checkbox/i).test(element.type);
	}	
	function hasAttr(jobj,attrname){
		return typeof(jobj.attr(attrname))!="undefined";
		
	}
	function findByName(ele, name) {
		if(!ele){
			ele = $(document);
		}
		return ele.find('[name="' + name + '"]');
	}
	function getLength(value, element) {
		switch( element.nodeName.toLowerCase() ) {
		case 'select':
			return $("option:selected", element).length;
		case 'input':
			if( checkable( element) ) {
				return findByName($(element).closest("._shieldjs_form_validate"), element.name).filter(':checked').length;
			}
		}
		return value.length;
	}
	
    function strlen(str,real){ //判断字符串长度，数字，字母，特殊符号占一个字节，汉字占2个字节
	     if(real)
		 {
		 	return strRealLen(str);
		 }else
		 {
		 	return str.length;
		 }
	}
	
    function strRealLen(str){ //判断字符串长度，数字，字母，特殊符号占一个字节，汉字占2个字节 
    	var len = 0;
    	for (var i=0; i<str.length; i++) {
    		var c = str.charCodeAt(i);
    		//单字节加1
    		if ((c >= 0x0001 && c <= 0x007e) || (0xff60<=c && c<=0xff9f)) {
    			len++;
    		} else {
    			len+=2;
    		}
    	}
    	return len;
	}
    $.fn.kvvalidate.format = function (source, params) {
		if ( arguments.length === 1 ) { //未定义params，则返回一个函数，参数值作为正则表达式中的内容
			return function() {
				var args = $.makeArray(arguments);
				args.unshift(source);
				return $.fn.kvvalidate.format.apply( this, args );
			};
		}
		if ( arguments.length > 2 && params.constructor !== Array  ) {
			params = $.makeArray(arguments).slice(1);
		}
		if ( params.constructor !== Array ) { //参数params不是数组，转换
			params = [ params ];
		}
		$.each(params, function(i, n) {
			source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
		});
		return source;
	}
    $.extend($.fn.kvvalidate, {
    	// 触发验证的方法
    	triggerMethod : ["blur"],//,"keyup"
    	// 默认参数
    	defaults : {
    			/** 是否弹出 */
    			alert: false
    			/** 遇到错误继续验证下一个表单项 */
    			,errorContinue : false
    			/** 验证未通过添加的样式 */
    			,errorClass : "input-error"
				/** 验证未通过tips的样式 */
    			,errorTipsClass : "error"
				/** 验证通过tips的样式 */
    			,successTipsClass : "success"
    			//,callback:function(success, ele, message, options){}/** 回调函数 ,4个参数，success是否成功，ele元素，message消息,options配置信息*/
    	},
    	// 扩展自定义方法
		addMethod : function(method, callback, msg){
			$.fn.kvvalidate.validatemethods[method] = callback;
			if(msg){
				$.fn.kvvalidate.errormessages[method] = msg;
			}
			else{
				$.fn.kvvalidate.errormessages[method] = checkFormmsgdef;
				
			}
		},
		// 是否为空值
		optional : function(value){
			return optional(value);
		},
		// 获取元素值的长度，如果为checkbox或radio则为选中个数
		getLength  : function(value, element){
			return getLength(value, element);
		},
		// 返回错误信息
		validateerr : function(jobj, msg){
			if(jobj.data("errormsg")){
				return jobj.data("errormsg");
			}
			var label = jobj.closest('td').prev().find('label').text();
			if(!label){
				label = jobj.attr("title");
			}
			var showmsg = msg;
			
			if(label){
				showmsg = label.replace(/：|\s|\*/g, '') +" "+ msg; //去除标点符号
			}
			return showmsg;
		},
		// 默认方法(可覆盖)
		validatemethods : {
			/*必填,不能为空及空字符串 eg：<input id='name' type='text' class='required'/>*/
			required : function(value) {
				//return !optional(value);
				return $.trim(value)!='';
			},
			/*手机+固定电话 eg：<input id='name' type='text' class='phone'/>*/
			phone : function (value) { 
				return optional(value) || /(^([0][1-9]{2,3}[-]?)?\d{3,8}(-\d{1,6})?$)|(^([0][1-9][0-9]{1,2}[-]?)?\d{3,8}(-\d{1,6})?$)|(^\([0][1-9]{2,3}\)\d{3,8}(\(\d{1,6}\))?$)|(^\d{3,8}$)|(^400[-]?\d{1,6}[-]?\d{1,6}$)|(^[1][\d][0-9]{9}$)|(^0[1][3,5][0-9]{9}$)/.test(value);
			},
			
			/*长度为{0}的字符（value为输入值element为当前验证dom元素param为参数【针对通过属性添加的验证】） eg：<input id='name' type='text' needLengths='10'/>*/
			needLengths : function (value, element, param) { 
				return optional(value) || getLength($.trim(value), element) >=param;
			},
			/*长度为{0}的字符（value为输入值element为当前验证dom元素param为参数【针对通过属性添加的验证】） eg：<input id='name' type='text' needMaxLengths='10'/>*/
			needMaxLengths : function (value,element,param) { 
				return optional(value) || getLength($.trim(value), element) <=param;
			},
			/*长度为{0}的字符（value为输入值element为当前验证dom元素param为参数【针对通过属性添加的验证】） eg：<input id='name' type='text' lengths='10'/>*/
			lengths : function (value, element, param) { 
				return optional(value) || getLength($.trim(value), element) == param;
			},
			/*最大长度为{0}的字符 eg：<input id='name' type='text' maxlengths='10'/>*/
		    maxlengths : function (value, element, param) { 
		        return optional(value) || strlen($.trim(value), false) <= param;
		    },	    
			/*长度为{0}-{1}的字符 eg：<input id='repwd' type='password' rangelength='1,5'/>长度为1-5*/
			rangelength : function (value, element, param) {
				var length  = getLength($.trim(value), element);
				return optional(value) || (length >= param[0] && length <=param[1]);
			},
			/*email验证  eg：<input id='name' type='text' class='email'/>*/
			email : function(value) {
				// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
				return optional(value) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value);
			},
			/*url验证  eg：<input id='name' type='text' class='url'/>*/
			url : function(value) {
				// contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
				return optional(value) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
			},
			/*数字范围验证，边界也包含  eg：<input id='name' type='text' numrange='10,20'/>*/
			numrange : function(value, element, param) {
				if (!optional(value)) { //非空
					var maxim = param[1] || 100000000, //结束值
					minim = param[0] || 0; //开始值
					value = value.replace(/\s*/g,"").replace(/,/g,"");
					if(!/^\d+\.?\d*$/.test(value)){ // 数字
						$.fn.kvvalidate.errormessages["numrange"] ="只能输入数字！";
						return false;
					}
					var valueNum = parseFloat(value);
					var minNum = parseFloat(minim);
					var maxNum = parseFloat(maxim);
					if (valueNum < minNum){ // 小数也能支持
						$.fn.kvvalidate.errormessages["numrange"] ="值不能小于"+minim+"！";
						return false;
					} else if (valueNum > maxNum) {
						$.fn.kvvalidate.errormessages["numrange"] ="值不能大于"+maxim+"！";
						return false;
					}
				}
				
				return true;
			},
			/*等于某元素的值 eg：<input id='repwd' type='password' equalTo='#pwd'/>*/
			equalTo : function(value, element, param) {
				var target = $(param);
				if(target.length >0){
					var showmsg = $.fn.kvvalidate.validateerr(target,'');
					$.fn.kvvalidate.errormessages["equalTo"] ="请输入与 "+showmsg+"相同的值";
					return value === target.val();
				}
				else 
					return true;
			}
		},
		// 默认提示(可覆盖)
		errormessages : {
			required : "不能为空",
			phone : "格式不正确",
			lengths : $.fn.kvvalidate.format("长度要求为{0}"),
			maxlengths : $.fn.kvvalidate.format("最大长度为{0}"),
			rangelength : $.fn.kvvalidate.format("长度要求为{0}到{1}"),
			needLengths : $.fn.kvvalidate.format("至少选择{0}个"),
			needMaxLengths : $.fn.kvvalidate.format("最多选择{0}个"),
			email : "格式不正确",
			url : "格式不正确",
			numrange:"",
			equalTo : "请输入相同的值"
		}
	});
	//---扩展方法---
	/*身份证号验证 eg：<input id='name' type='text' class='idnum'/>*/
	/* 参考：http://www.cnblogs.com/lzrabbit/archive/2011/10/23/2221643.html
	根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定，公民身份号码是特征组合码，由十七位数字本体码和一位数字校验码组成。排列顺序从左至右依次为：六位数字地址码，八位数字出生日期码，三位数字顺序码和一位数字校验码。
	    地址码表示编码对象常住户口所在县(市、旗、区)的行政区划代码。
	    出生日期码表示编码对象出生的年、月、日，其中年份用四位数字表示，年、月、日之间不用分隔符。
	    顺序码表示同一地址码所标识的区域范围内，对同年、月、日出生的人员编定的顺序号。顺序码的奇数分给男性，偶数分给女性。
	    校验码是根据前面十七位数字码，按照ISO 7064:1983.MOD 11-2校验码计算出来的检验码。

	出生日期计算方法。
	    15位的身份证编码首先把出生年扩展为4位，简单的就是增加一个19或18,这样就包含了所有1800-1999年出生的人;
	    2000年后出生的肯定都是18位的了没有这个烦恼，至于1800年前出生的,那啥那时应该还没身份证号这个东东，⊙﹏⊙b汗...
	下面是正则表达式:
	 出生日期1800-2099  (18|19|20)?\d{2}(0[1-9]|1[12])(0[1-9]|[12]\d|3[01])
	 身份证正则表达式 /^\d{6}(18|19|20)?\d{2}(0[1-9]|1[12])(0[1-9]|[12]\d|3[01])\d{3}(\d|X)$/i            
	 15位校验规则 6位地址编码+6位出生日期+3位顺序号
	 18位校验规则 6位地址编码+8位出生日期+3位顺序号+1位校验位
	 
	 校验位规则     公式:∑(ai×Wi)(mod 11)……………………………………(1)
	                公式(1)中： 
	                i----表示号码字符从由至左包括校验码在内的位置序号； 
	                ai----表示第i位置上的号码字符值； 
	                Wi----示第i位置上的加权因子，其数值依据公式Wi=2^(n-1）(mod 11)计算得出。
	                i 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
	                Wi 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 1
	*/
	//身份证号合法性验证 
	//支持15位和18位身份证号
	//支持地址编码、出生日期、校验位验证
	$.fn.kvvalidate.addMethod("idnum",function (value) { 
		var AF={11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外"};
		var AG = 0;
		var AH = value;
		AH = AH.toLowerCase().replace("x", "a");
		var AI = AH.length;
		var AJ="";
		if(AH != ""){
			if(AF[parseInt(AH.substr(0,2))]==null){
				$.fn.kvvalidate.errormessages["idnum"] = "地区不正确";
				return false;
			}
			if (AI==18){
				AJ=AH.substr(6,4)+"-"+Number(AH.substr(10,2))+"-"+Number(AH.substr(12,2));   
				var d = new Date(AJ.replace(/-/g,"/")); 
				if(AJ!=(d.getFullYear()+"-"+ (d.getMonth()+1) + "-" + d.getDate())){
					$.fn.kvvalidate.errormessages["idnum"] = "生日不正确";
					return false;
				}  
				for(var i = 17;i>=0;i--)   
					AG += (Math.pow(2,i) % 11) * parseInt(AH.charAt(17 - i),16);
				if(AG%11!=1){
					$.fn.kvvalidate.errormessages["idnum"] = "证件号不存在";
					return false;
				};
			}else if (AI==15){   
				AJ = "19" + AH.substr(6,2) + "-" + Number(AH.substr(8,2)) + "-" + Number(AH.substr(10,2));
				var d = new Date(AJ.replace(/-/g,"/"));
				var dd = d.getFullYear().toString() + "-" + (d.getMonth()+1) + "-" + d.getDate();      
				if(AJ != dd){
					$.fn.kvvalidate.errormessages["idnum"] ="生日不正确";
					return false;
				};
			}else{
				$.fn.kvvalidate.errormessages["idnum"] ="长度不正确";
				return false;
			}	
		}
		return true;
	});
	/*香港地区身份证号验证,参考资料：http://shenfenzheng.bajiu.cn/?rid=40 */
	$.fn.kvvalidate.addMethod("hkidnum",function (value) {
		var AH = value;
		var AI = AH.length;
		if(AH != ""){
			if (AI==8){
				/* 先把首位字母改为数字，即A为1，B为2，C为3...Z为26，再乘以8; */
				var hash = (AH.toUpperCase().charCodeAt() - 64) * 8;
				/* 然后把字母后面的6个数字依次乘以7、6、5、4、3、2；再将以上所有乘积相加的和，除以11，得到余数； */
                for (var i = 1;i<=6;i++) {
                    hash += parseInt(AH.substring(i,i+1)) * (8-i)
                }
               	/* 如果整除，则括号中的校验码为0，如果余数为1，则校验码为A，如果余数为2～10，则用11减去这个余数的差作校验码。 */
                var validateNo = hash%11;
                var ret = "";
                if (validateNo==0) {
                    ret = 0;
                }else if (validateNo==1) {
                    ret = "A";
                } else {
                    ret = 11 - validateNo;
                }
                var AR = AH.substring(0,AH.length - 1) + ret;
                if(AH != AR){
					$.fn.kvvalidate.errormessages["hkidnum"] = "证件号不存在";
					return false;
                }
			}else{
				$.fn.kvvalidate.errormessages["hkidnum"] ="长度不正确";
				return false;
			}	
		}
		return true;
	});
	/*台湾地区身份证号验证 */
	/*
	 * 首位数字代表性别，男性为1、女性为2;最后一位数字是检验码;其它是电脑系统给码。
	*/
	$.fn.kvvalidate.addMethod("twidnum",function (value) {
		var AH = value;
		var AI = AH.length;
		if(AH != ""){
			if (AI==10){
				var AF={"A":10,"B":11,"C":12,"D":13,"E":14,"F":15,"G":16,"H":17,"J":18,"K":19,"L":20,"M":21,"N":22,"P":23,"Q":24,"R":25,"S":26,"T":27,"U":28,"V":29,"X":30,"Y":31,"W":32,"Z":33,"I":34,"O":35};
				var start = AH.substring(0, 1);
				/* 先查找首位字母对应的数字数字，即A为10，B为11，C为3...Z为33; */
				var iStart = AF[start];
				if (iStart == null) {
					$.fn.kvvalidate.errormessages["twidnum"] = "地区不正确";
					return false;
				}
		        var end = AH.substring(9, 10);
				/* 通算值= 首字母对应的第一位验证码+ 首字母对应的第二位验证码 * 9 + 性别码 * 8 + 第二位数字 * 7 + 第三位数字 * 6 + 第四位数字 * 5 + 第五位数字 * 4 + 第六位数字 * 3 + 第七位数字 * 2 + 第八位数字 * 1
				最后一位数 =10- 通算值的末尾数。 */
				var hash = parseInt(iStart / 10) + (iStart % 10) * 9;
				for (var i =1;i<=9;i++) {
					hash += parseInt(AH.substring(i,i+1)) * (8-i+1);
				}
               	/* 如果整除，则括号中的校验码为0，其他的则用10减去这个余数的差作校验码。 */
                var validateNo = hash % 10;
                var ret = "";
                if (validateNo==0) {
                    ret = 0;
                } else {
                    ret = 10 - validateNo;
                }
                var AR = parseInt(end);
                if(ret != AR){
					$.fn.kvvalidate.errormessages["twidnum"] = "证件号不存在";
					return false;
                }
			}else{
				$.fn.kvvalidate.errormessages["twidnum"] ="长度不正确";
				return false;
			}	
		}
		return true;
	});
    /*统一社会信用代码 eg：<input id='name' type='text' class='socialCreditCode'/>*/
    $.fn.kvvalidate.addMethod("socialCreditCode", function (value) {
            return optional(value) || /[^_IOZSVa-z\W]{2}\d{6}[^_IOZSVa-z\W]{10}/g.test(value);
    });
	/*固定电话 eg：<input id='name' type='text' class='tphone'/>*/
	$.fn.kvvalidate.addMethod("tphone",function(value) { 
		return optional(value) || /(^([0][1-9]{2,3}[-]?)?\d{3,8}(-\d{1,6})?$)|(^\([0][1-9]{2,3}\)\d{3,8}(\(\d{1,6}\))?$)|(^\d{3,8}$)|(^400[-]?\d{1,6}[-]?\d{1,6}$)/.test(value);
	});
	/*手机 eg：<input id='name' type='text' class='mphone'/>*/
	$.fn.kvvalidate.addMethod("mphone",function (value) { 
		return optional(value) || /(^[1][\d][0-9]{9}$)|(^0[1][3,5][0-9]{9}$)/.test(value);
	});
	/*整数 eg：<input id='name' type='text' class='integer'/>*/
	$.fn.kvvalidate.addMethod("integer",function (value) { 
		return optional(value) || /^(\+|-)?\d+$/.test(value);
	}, "请输入整数");
	/*0-1的小数  eg：<input id='name' type='text' class='decimal'/>*/
    $.fn.kvvalidate.addMethod("decimal ",function (value) { 
        return optional(value) || /0\.[0-9]+/.test(value);
    }, "请输入0-1的小数");
	/*数字，包含科学计数法，如7.823E5（即782300） eg：<input id='name' type='text' class='number'/>*/
	$.fn.kvvalidate.addMethod("number", function(value) { 
		// return optional(value) || /^\d+($|\.\d+$)/.test(value);
		return optional(value) || /^-?\d+($|\.\d+([Ee]?\d+)?$)/.test(value);
	}, "请输入数字");
	/*数字包含负数 eg：<input id='name' type='text' class='isnumber'/>*/ 
	$.fn.kvvalidate.addMethod("isnumber", function(value) { 
	    return optional(value) || /^-?[1-9]+(\.\d+)?$|^-?0(\.\d+)?$|^-?[1-9]+[0-9]*(\.\d+)?$/.test(value);
	}, "请输入数字");        
	/*汉字 eg：<input id='name' type='text' class='chchar'/>*/
	$.fn.kvvalidate.addMethod("chchar",function (value) { 
		return optional(value) || /^[\u4e00-\u9fa5]+$/.test(value);
	},"请输入汉字");
	/*请输入汉字、字母或数字  eg：<input id='name' type='text' class='charwordnum'/>*/
	$.fn.kvvalidate.addMethod("charwordnum",function (value) { 
		return optional(value) || /^[0-9a-zA-Z\u4e00-\u9fa5_]+$/.test(value);
	},"请输入汉字、字母、数字或下划线");
	$.fn.kvvalidate.addMethod("username",function (value) { 
		return optional(value) || /^[0-9a-zA-Z_]+$/.test(value);
	},"请输入字母、数字或下划线");
	/*ip4  eg：<input id='name' type='text' class='ipv4'/>*/
	$.fn.kvvalidate.addMethod("ipv4", function(value) {
		return optional(value) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(value);
	}, "请输入正确的IP地址.");
	/*ip6  eg：<input id='name' type='text' class='ipv6'/>*/
	$.fn.kvvalidate.addMethod("ipv6", function(value) {
		return optional(value) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value);
	}, "请输入正确的IP v6地址.");
	/*非空（空格及不填）字符  eg：<input id='name' type='text' class='notBlank'/>*/
	$.fn.kvvalidate.addMethod("notBlank", function(value) {
		return optional(value) || $.trim(value)!='';
	}, "不能为空");
	$.fn.kvvalidate.addMethod("notEmpty", function(value) {
		return !optional(value);
	}, "不能为空");
	/*年月日  eg：<input name='date' type='text' class='date'/>*/
	$.fn.kvvalidate.addMethod("date",function(value) { 
		return optional(value) || /^(\d{4})-(\d{2})-(\d{2})$/.test(value);
	}, "请输入2017-01-01格式的日期.");
	/*年月日或年月以短横线连接 eg：<input name='date' type='text' class='dateOrMonth'/>*/
	$.fn.kvvalidate.addMethod("dateOrMonth",function(value) { 
		return optional(value) || /^(\d{4})-(\d{2})(-(\d{2}))?$/.test(value);
	}, "请输入2017-01-01或2017-01格式的日期格式.");
	/*验证验证码是否正确  eg：<input id='name' type='text' class='authCode'/>*/
	$.fn.kvvalidate.addMethod("authCode", function(value) {
		var flag=false;
		$.ajax({
			async:false,
	        type : "post",
	        url : "/checkAuthCode",
	        data : "random=" + value,
	        success : function(result) {
	            result = eval(result);
	            if (!result) {
	                flag= false;
	            }
	            else{
	                flag= true;
	            }
	        }
	    });
		return flag;
	}, "错误");
})(jQuery);
